using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using CppWrapper.Interfaces;

namespace CppWrapper.GeneratorService
{
  [Serializable]
  public class Generator : MarshalByRefObject, IGenerator
  {
    [NonSerialized]
    private readonly TypeDependencyHandler typeDependency;
    [NonSerialized]
    private GeneratorServices generatorServices;
    [NonSerialized]
    private readonly string targetDirectory;

    public event ProgressHandler Progress;

    static Generator()
    {
      System.Diagnostics.Debug.WriteLine(AppDomain.CurrentDomain.FriendlyName);
      AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += OnCurrentDomainReflectionOnlyAssemblyResolve;
    }

    public Generator(string targetDirectory, bool resolveDependencies)
    {
      this.targetDirectory = targetDirectory;
      typeDependency = new TypeDependencyHandler(resolveDependencies);
    }

    internal GeneratorServices GeneratorServices
    {
      get
      {
        if (generatorServices==null)
        {
          generatorServices = new GeneratorServices(typeDependency);
        }
        return generatorServices;
      }
    }

    static Assembly OnCurrentDomainReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
    {
      return Assembly.Load(args.Name);
    }

    public IList<ITypeInfo> GetTypesFromAssembly(string assemblyFilePath)
    {
      System.Diagnostics.Debug.WriteLine(AppDomain.CurrentDomain.FriendlyName);
      Assembly assembly = GetAssembly(assemblyFilePath);
      return GetTypesFromAssembly(assembly);
    }

    public IList<ITypeInfo> GetTypesFromAssembly(IAssemblyInfo info)
    {
      Assembly assembly = GetAssembly(info);
      return GetTypesFromAssembly(assembly);
    }

    private static IList<ITypeInfo> GetTypesFromAssembly(Assembly assembly)
    {
      List<ITypeInfo> typeList = new List<ITypeInfo>();
      foreach (Type exportedType in assembly.GetExportedTypes())
      {
        TypeInfo info = new TypeInfo(exportedType.Name, exportedType.Namespace, GetTypeDescription(exportedType), exportedType.FullName, exportedType.Assembly.CodeBase);
        typeList.Add(info);
      }
      typeList.Sort(CompareTypes);
      return typeList;
    }

    private static int CompareTypes(ITypeInfo a, ITypeInfo b)
    {
      return a.Name.CompareTo(b.Name);
    }

    private static Assembly GetAssembly(string path)
    {
      try
      {
        return Assembly.LoadFrom(path);
      }
      catch(FileNotFoundException)
      {
        return Assembly.Load(path);
      }
    }

    private static Assembly GetAssembly(IAssemblyInfo info)
    {
      AssemblyName assemblyName = new AssemblyName(info.Name);
      assemblyName.SetPublicKeyToken(info.PublicKeyToken);
      assemblyName.Version = info.Version;
      return Assembly.Load(info.Full);
    }

    private static string GetTypeDescription(Type exportedType)
    {
      if (exportedType.IsClass)
      {
        return "Class";
      }
      else if (exportedType.IsInterface)
      {
        return "Interface";
      }
      else if (exportedType.IsValueType)
      {
        return "Struct";
      }
      return "";
    }

    public void Generate(IList<ITypeInfo> typeInfos, AbortHandler cancellationRequested)
    {
      DependentFilesWriter.CopyDependentFilesToOutputDirectory(targetDirectory);
      int nCurrent = 0;
      int nTotal = typeInfos.Count;
      foreach (ITypeInfo typeInfo in typeInfos)
      {
        if (null != cancellationRequested && cancellationRequested())
          return;

        nCurrent++;
        Assembly assembly = Assembly.LoadFrom(typeInfo.AssemblyPath);

        foreach (Type exportedType in assembly.GetExportedTypes())
        {
          if (null != cancellationRequested && cancellationRequested())
            return;

          if (typeInfo.Name == exportedType.Name && typeInfo.Namespace == exportedType.Namespace)
          {
            try
            {
              typeDependency.AddType(exportedType);
              while (typeDependency.DependentTypes.Count>0)
              {
                Type type = typeDependency.DependentTypes[0];
                WrapType(type);
                typeDependency.MarkasWrapped(type);
              }
            }
            catch(Exception e)
            {
              System.Diagnostics.Debug.WriteLine(e.ToString());
            }
          }
        }
        if (null != Progress)
          Progress((nCurrent / nTotal) * 100);
      }
    }

    private void WrapType(Type type)
    {
      bool isWrapped;
      if (type.IsGenericTypeDefinition)
      {
        return;
      }
      string wrappedClassTypeName = GeneratorServices.GetWrappedTypeName(type, true, false, out isWrapped);
      if (!isWrapped)
      {
        return;
      }

      if (type.BaseType!=null && type.BaseType == typeof(Enum))
      {
        WrapEnum(type);
        return;
      }
      StringBuilder cppFileContents=new StringBuilder();
      StringBuilder hFileContents = new StringBuilder();

      StringBuilder cppGlobalContents = new StringBuilder();
      StringBuilder hGlobalContents = new StringBuilder();

      BeginHeaderFile(hFileContents, type);
      BeginCppFile(cppFileContents, type);

      List<MemberInfo> typeMembers = GeneratorServices.GetTypeMembers(type);
      Dictionary<string, PropertyInfo> propertyMethodDictionary = new Dictionary<string, PropertyInfo>();
      foreach (MemberInfo memberInfo in typeMembers)
      {

        if (memberInfo.MemberType == MemberTypes.Property)
        {
          PropertyInfo propInfo = (PropertyInfo) memberInfo;
          MethodInfo setMethod = propInfo.GetSetMethod();
          if (setMethod != null)
          {
            propertyMethodDictionary.Add(setMethod.Name, propInfo);
          }
          MethodInfo getMethod = propInfo.GetGetMethod();
          if (getMethod!=null)
          {
            propertyMethodDictionary.Add(getMethod.Name, propInfo);
          }
          if (!GeneratorServices.IgnoreMember(memberInfo))
          {
            WrapProperty(hFileContents, propInfo);
          }
        }
      }

      foreach (MemberInfo memberInfo in typeMembers)
      {
        if (GeneratorServices.IgnoreMember(memberInfo))
        {
          continue;
        }


        if (memberInfo.MemberType == MemberTypes.Method)
        {
          if (memberInfo.Name.StartsWith("op_"))
          {
            WrapOperator(memberInfo, cppGlobalContents, hGlobalContents, cppFileContents, hFileContents);
          }
          else
          {
            PropertyInfo propertyInfo;
            if (propertyMethodDictionary.TryGetValue(memberInfo.Name, out propertyInfo))
            {
              if (GeneratorServices.IgnoreMember(propertyInfo))
              {
                continue;
              }
            }
            WrapMethod(hFileContents, cppFileContents, memberInfo, wrappedClassTypeName, propertyInfo);
          }
        }
        else if (memberInfo.MemberType == MemberTypes.Constructor)
        {
          WrapConstructor(hFileContents, cppFileContents, memberInfo, type);          
        }
        else if (memberInfo is FieldInfo)
        {
          WrapLiteralField((FieldInfo) memberInfo, cppFileContents, hFileContents);
        }
      }

      EndHeaderFile(hFileContents, type, hGlobalContents);
      EndCppFile(cppFileContents, type, cppGlobalContents);

      File.WriteAllText(Path.Combine(targetDirectory, GeneratorServices.GetImplementationFilename(type)), cppFileContents.ToString());
      File.WriteAllText(Path.Combine(targetDirectory, GeneratorServices.GetHeaderFilename(type)), hFileContents.ToString());
    }

    private void WrapProperty(StringBuilder hFileContents, PropertyInfo propInfo)
    {
      if (propInfo.Name=="Item")
      {
        return;
      }
      MethodInfo getMethod = propInfo.GetGetMethod();
      MethodInfo setMethod = propInfo.GetSetMethod();
      if (setMethod==null && getMethod==null) { return; }
      string getDeclaration = GeneratorServices.GetCppPropertyAccessorDeclaration(getMethod, "get");
      string setDeclaration = GeneratorServices.GetCppPropertyAccessorDeclaration(setMethod, "put");
      string getSetSeparator = GeneratorServices.GetCppPropertyAccessorSeparator(setDeclaration, getDeclaration);
      GeneratorServices.InsertCppPropertyDeclarationIntoHeaderFile(propInfo, setDeclaration, getDeclaration, hFileContents, getSetSeparator);
    }

    private void WrapLiteralField(FieldInfo fieldInfo, StringBuilder cppFileContents, StringBuilder hFileContents)
    {
      bool wrapped;
      string wrappedTypeName = GeneratorServices.GetWrappedTypeName(fieldInfo.FieldType, true, true, out wrapped);
      string name = string.Format("Get_{0}", fieldInfo.Name);
      string className = GeneratorServices.GetWrappedTypeName(fieldInfo.DeclaringType, true, false, out wrapped);
      string cliClassName = generatorServices.GetCliTypeName(fieldInfo.DeclaringType, false);

      hFileContents.AppendFormat("  WREXPORT static {1} {0}();", name, wrappedTypeName);
      cppFileContents.AppendFormat(@"{2} {0}::{1}(){{
      return {3}::{4};
    }};
  ", className, name, wrappedTypeName, cliClassName, fieldInfo.Name);
    }

    private void WrapOperator(MemberInfo memberInfo, StringBuilder cppGlobalContents, StringBuilder hGlobalContents, StringBuilder cppClassContents, StringBuilder hClassContents)
    {
      MethodInfo methodInfo = (MethodInfo) memberInfo;
      Type returnType = methodInfo.ReturnType;
      ParameterInfo[] parameters = methodInfo.GetParameters();
      Type parameter1Type = parameters.Length > 0 ? parameters[0].ParameterType : null;
      Type parameter2Type = parameters.Length > 1 ? parameters[1].ParameterType : null;

      string operatorName;
      switch(memberInfo.Name)
      {
        case "op_Addition":
          operatorName = "+";
          break;
        case "op_Equality":
          operatorName = "==";
          break;
        case "op_GreaterThan":
          operatorName = ">";
          break;
        case "op_GreaterThanOrEqual":
          operatorName = ">=";
          break;
        case "op_Inequality":
          operatorName = "!=";
          break;
        case "op_LessThan":
          operatorName = "<";
          break;
        case "op_LessThanOrEqual":
          operatorName = "<=";
          break;
        case "op_Subtraction":
          operatorName = "-";
          break;
        case "op_Implicit":
        case "op_Explicit":
          WrapImplicitOperator(methodInfo, cppClassContents, hClassContents);
          return;
        default:
          System.Diagnostics.Debug.WriteLine(string.Format("Operator {0} not wrapped", memberInfo.Name));
          return;
      }
      bool isWrappedType;
      string wrappedReturnTypeName = GeneratorServices.GetWrappedTypeName(returnType, true, true, out isWrappedType);
      string wrappedParameter1TypeName = GeneratorServices.GetWrappedTypeName(parameter1Type, true, true, out isWrappedType);
      string wrappedParameter2TypeName = GeneratorServices.GetWrappedTypeName(parameter2Type, true, true, out isWrappedType);
      string returnStatement = string.Format("return a.ThisObject() {0} b.ThisObject();", operatorName);
      cppGlobalContents.AppendFormat(
        @"{1} const operator{0}({2} const& a, {3} const& b)
{{
  {4}
}}
", operatorName, wrappedReturnTypeName, wrappedParameter1TypeName, wrappedParameter2TypeName, EncapsulateInExceptionHandling(returnStatement));
      hGlobalContents.AppendFormat(@"WREXPORT {1} const operator{0}({2} const& a, {3} const& b);
", operatorName, wrappedReturnTypeName, wrappedParameter1TypeName, wrappedParameter2TypeName);
    }

    private void WrapImplicitOperator(MethodInfo methodInfo, StringBuilder cppClassContents, StringBuilder hClassContents)
    {
      bool isWrapped;
      string wrappedClassName =
        GeneratorServices.GetWrappedTypeName(methodInfo.DeclaringType, true, false, out isWrapped);
      if (methodInfo.ReturnType != methodInfo.DeclaringType)
      {
        Type returnType = methodInfo.ReturnParameter.ParameterType;
        string toTypeName =
          GeneratorServices.GetWrappedTypeName(returnType, true, true, out isWrapped);
        if (returnType.IsEnum)
        {
          toTypeName += "::Enum";
        }
        hClassContents.AppendFormat(@"  WREXPORT operator {0}();
", toTypeName);
        string statement = GeneratorServices.InsertManaged2NativeMarshalling(string.Format("(({0}) (*this).ThisObject())", GeneratorServices.GetCliTypeName(returnType, true)), returnType);
        string returnStatement = string.Format("return {0};", statement);
        cppClassContents.AppendFormat(@"{0}::operator {1}()
{{
    {2}
}}
",wrappedClassName, toTypeName, EncapsulateInExceptionHandling(returnStatement));
      }
      else
      {
        bool fromTypeWrapped;
        Type fromType = methodInfo.GetParameters()[0].ParameterType;
        string fromTypeName =
          GeneratorServices.GetWrappedTypeName(fromType, true, true, out fromTypeWrapped);
        if (fromType.IsEnum)
        {
          fromTypeName += "::Enum";
        }
        hClassContents.AppendFormat(@"  WREXPORT void operator=({0} i);
", fromTypeName);
        cppClassContents.AppendFormat(@"void {0}::operator=({1} i)
{{
  m_handle = {2};
}}
", wrappedClassName, fromTypeName, GeneratorServices.InsertNative2ManagedMarshalling("i", fromType));
      }
    }

    private void WrapEnum(Type type)
    {
      StringBuilder hFileContents = new StringBuilder();
      hFileContents.AppendLine(@"#pragma once");
      bool wrapped;
      hFileContents.AppendLine(GeneratorServices.GetWrapperStartNamespaceDeclaration(type));
      hFileContents.AppendLine();
      hFileContents.AppendFormat("class {0} {{", GeneratorServices.GetWrappedTypeName(type, false, false, out wrapped));
      hFileContents.AppendLine();
      hFileContents.AppendLine("public:");
      hFileContents.AppendLine("enum Enum {");
      bool firstField = true;
      foreach (FieldInfo fieldInfo in type.GetFields())
      {
        if (!fieldInfo.IsStatic && !fieldInfo.IsLiteral)
        {
          continue;
        }
        if (!firstField)
        {
          hFileContents.Append(" ,");
        }
        hFileContents.Append(fieldInfo.Name);
        hFileContents.Append("=");
        hFileContents.Append(fieldInfo.GetRawConstantValue().ToString());
        firstField = false;
      }
      hFileContents.AppendLine("};};");
      hFileContents.AppendLine(GeneratorServices.GetWrapperEndNamespaceDeclaration(type));

      File.WriteAllText(Path.Combine(targetDirectory, GeneratorServices.GetHeaderFilename(type)),
                        hFileContents.ToString());
    }

    private void WrapConstructor(StringBuilder hFileContents, StringBuilder cppFileContents, MemberInfo memberInfo, Type type)
    {
      ConstructorInfo info = (ConstructorInfo) memberInfo;
      bool isWrapped;
      string wrappedTypeNameWithTemplate = GeneratorServices.GetWrappedTypeName(type, true, false, out isWrapped);
      string parameterDeclaration = GeneratorServices.GetMethodParameter(true, info.GetParameters());
      hFileContents.AppendFormat(@"  WREXPORT {0}({1});", wrappedTypeNameWithTemplate, parameterDeclaration);
      hFileContents.AppendLine();
      string newStatement= type.IsValueType ? string.Empty : "gcnew ";
      cppFileContents.AppendFormat(
        @"{0}::{0}({2})
{{
  try
  {{
    m_handle = {4}{1}({3});
  }}
  catch(Exception^ e)
  {{
    throw CMException(e);
  }}
}}
",
        wrappedTypeNameWithTemplate, GeneratorServices.GetCliTypeName(type, false), parameterDeclaration,
        GeneratorServices.GetMethodParameter(false, info.GetParameters()),
        newStatement);
    }

    private void WrapMethod(StringBuilder hFileContents, StringBuilder cppFileContents, MemberInfo memberInfo, string wrappedClassTypeName, PropertyInfo propertyInfo)
    {
      MethodInfo methodInfo = (MethodInfo) memberInfo;
      string name = memberInfo.Name;
      Type methodReturnType = methodInfo.ReturnType;
      bool isWrapped;
      string wrappedTypeName = GeneratorServices.GetWrappedTypeName(methodReturnType, true, true, out isWrapped);
      if (GeneratorServices.IsEnum(methodReturnType))
      {
        wrappedTypeName = wrappedTypeName + "::Enum";
      }
      string parameterDeclaration = GeneratorServices.GetMethodParameter(true, methodInfo.GetParameters());
      string headerLine;
      if (methodInfo.IsStatic)
      {
        headerLine = string.Format("  WREXPORT static {1} {0}({2});", name, wrappedTypeName, parameterDeclaration);
      }
      else
      {
        headerLine = string.Format("  WREXPORT {1} {0}({2}) const;", name, wrappedTypeName, parameterDeclaration);
      }
      hFileContents.AppendLine(headerLine);
      bool hasReturnValue = methodReturnType != typeof (void);
      string parameterCall = GeneratorServices.GetMethodParameter(false, methodInfo.GetParameters());
      string statement;
      bool isPropertyImplementation = propertyInfo!=null;
      string methodCallPrefix;
      if (methodInfo.IsStatic)
      {
        methodCallPrefix = GeneratorServices.GetCliTypeName(methodInfo.DeclaringType, false) + "::";
      }
      else
      {
        if (memberInfo.DeclaringType.IsValueType)
        {
          methodCallPrefix = "(*this).ThisObject().";
        }
        else
        {
          methodCallPrefix = "(*this)->";
        }
      }
      if (!isPropertyImplementation)
      {
        statement = string.Format("{2}{0}({1})", name, parameterCall, methodCallPrefix);
      }
      else if (hasReturnValue)
      {
        if (propertyInfo.Name == "Item")
        {
          statement = string.Format("{0}default[index]", methodCallPrefix);
        }
        else
        {
          statement = string.Format("{1}{0}", propertyInfo.Name, methodCallPrefix);
        }
      }
      else
      {
        if (propertyInfo.Name == "Item")
        {
          Type type = methodInfo.GetParameters()[1].ParameterType;
          statement = string.Format("{0}default[index] = {1}", methodCallPrefix, GeneratorServices.InsertNative2ManagedMarshalling("value", type));
        }
        else
        {
          statement = string.Format("{2}{0} = {1}", propertyInfo.Name, parameterCall, methodCallPrefix);
        }
      }
      if (hasReturnValue)
      {
        statement = string.Format("return {0}", GeneratorServices.InsertManaged2NativeMarshalling(statement, methodReturnType));
      }
      statement = EncapsulateInExceptionHandling(statement);
      string implementation = string.Format(@"{6}{0} {2}::{1}({3}) {5}
{{
  {4}
}}
", wrappedTypeName, name, wrappedClassTypeName,
                      parameterDeclaration, statement, methodInfo.IsStatic ? "" : "const", GeneratorServices.GetCliTemplateDefinition(methodInfo));
      cppFileContents.AppendLine(implementation);
    }

    private static string EncapsulateInExceptionHandling(string contents)
    {
      return string.Format(@"try {{
{0};
}}
catch (Exception^ e) {{
  throw CMException(e);
}}
", contents);
    }
    private void BeginCppFile(StringBuilder cppFileContents, Type type)
    {
      bool wrapped;
      cppFileContents.AppendFormat(
        @"{3}
#include ""stdafx.h""
#include ""CMException.h""
#include ""{0}""
{2}
{1}

{4} {4}::DynamicCast(const ::System::CMObject& object)
{{
  return {4}(safe_cast<{5}> (object.ThisObject()) );
}}

{4} {4}::GetWrappedNull()
{{
  return DynamicCast(CMObject());
}}

",
        GeneratorServices.GetHeaderFilename(type), 
        GeneratorServices.GetWrapperStartNamespaceDeclaration(type),
        GeneratorServices.GetIncludeStatements(type, true), 
        disclaimer,
        GeneratorServices.GetWrappedTypeName(type, true, false, out wrapped), 
        GeneratorServices.GetCliTypeName(type, true));
    }


    private const string disclaimer =
      @"// This file was automatically generated 
// DO NOT MODIFY THIS FILE
// please use the C++ Wrapper Generator to create a new version of this file
// DONT CHANGE THE GENERATED FILE CHANGE THE GENERATOR";

    private void BeginHeaderFile(StringBuilder hFileContents, Type type)
    {
      bool isWrapped;
      string wrappedTypeName = GeneratorServices.GetWrappedTypeName(type, true, false, out isWrapped);
      bool isBaseWrapped;
      string wrappedBaseTypeName = GeneratorServices.GetWrappedTypeName(typeof(object), false, true, out isBaseWrapped);
      if (type.IsGenericType)
      {
        GeneratorServices.GetCliTemplateDefinition(type);
      }
      hFileContents.AppendFormat(
        @"{3}
#pragma once

#include ""Wrap.h""
{2}
#ifdef _MANAGED
using namespace System::Runtime::InteropServices;
#endif

{1}

class {0} : public ::System::CMObject
{{
public:
",
        wrappedTypeName,
        GeneratorServices.GetWrapperStartNamespaceDeclaration(type),
        GeneratorServices.GetIncludeStatements(type, false),
        disclaimer);
      //if (!type.IsAbstract || type.IsInterface)
      {
        hFileContents.AppendFormat(
          @"
  {0}& operator=(const {0} &r)                
  {{                                      
    {1}::operator=(r);                      
    return *this;                            
  }}                                      

  WREXPORT static {0} DynamicCast(const ::System::CMObject& object);
  WREXPORT static {0} GetWrappedNull();

#ifdef _MANAGED
  {0}({2} o) : {1}(o) {{ }}                    
  {2} ThisObject() const                        
  {{                                      
    return static_cast<{2}>((Object^)m_handle);        
  }}                                      
  {2} operator->() const                        
  {{                                      
    return ThisObject();                        
  }}
  {0}& operator=({2} o)
  {{
    m_handle = o;                            
    return *this;                            
  }}
#endif
",
          wrappedTypeName,
          wrappedBaseTypeName,
          GeneratorServices.GetCliTypeName(type, true));
      }
    }

    private void EndCppFile(StringBuilder cppFileContents, Type type, StringBuilder cppGlobalContents)
    {
      cppFileContents.AppendLine();
      cppFileContents.AppendLine(cppGlobalContents.ToString());
      cppFileContents.AppendLine(GeneratorServices.GetWrapperEndNamespaceDeclaration(type));
    }

    private void EndHeaderFile(StringBuilder hFileContents, Type type, StringBuilder hGlobalContents)
    {
      hFileContents.AppendFormat(@"
}};

{1}

{0}
", GeneratorServices.GetWrapperEndNamespaceDeclaration(type), hGlobalContents);
    }


  }
}